use std::fmt::Display;
use std::collections::HashMap;
use std::path::Path;

use regex::Regex;
use select::document::Document;
use select::predicate::Name;
use crate::ChainData;
use crate::Color;

lazy_static!{
    static ref ID_RE:Regex = Regex::new(r#"(#[\w\-]+)"#).unwrap();
    static ref CLASSS_RE:Regex = Regex::new(r#"(\.[\w\-]+)"#).unwrap();  
    static ref ATTR_RE:Regex = Regex::new(r#"(\[(.+)\]+)"#).unwrap(); 
    static ref WRAPER: Regex = Regex::new(r#"(\[.+?\])"#).unwrap(); 
}


pub enum Wraper{
    Single(String),
    Multi(Vec<String>)
}



#[allow(dead_code)]
impl Wraper{
    //!
    //!suport   to_re("x")
    //!         to_grep("xx")
    //!         to_json(" ","=")
    //!         to_split("\n")
    //!         to_jq(".some.some")
    //!         to_append("xxx") # append in each line.
    //!         to_replace("from", "to") # replace by regex in each line.
    //!         to_css("xxx ")
    //!             if "|" in arg mean return html
    //!             if "[attr]" in mean return attr value
    //!            
    //! 
    pub fn merge_with(&self, with_str:&str) -> Self{
        match self{
            Self::Multi(vs) => Self::Single(vs.iter().map(|x| format!("{}",x)).collect::<Vec<String>>().join(with_str)),
            Self::Single(v) => Self::Single(v.to_string())
        }
    }

    pub fn to_append(&self, extend_str:&str) -> Self{
        match self{
            Self::Single(_) => self.to_split("\n").to_append(extend_str),
            Self::Multi(vs) => {
                Self::Multi(vs.iter().map(|x| format!("{}{}",x, extend_str)).collect())
            }
        }
    }
    pub fn to_replace(&self, fr:&str, to: &str) -> Self{
        if let Ok(f) = Regex::new(fr){
            match self{
                Self::Single(_) => self.to_split("\n").to_replace(fr, to),
                Self::Multi(vs) => {
                    Self::Multi(vs.iter().map(|x| f.replace_all(x, to).to_string()).collect())
                }
            }
        }else{
            match self{
                Self::Single(v) => Self::Single(v.to_owned()),
                Self::Multi(vs) => Self::Multi(vs.to_owned())
            }
        }

    }
    pub fn to_grep(&self, key:&str) -> Self{
        match self{
            Self::Single(_) => {
                self.to_split("\n").to_grep(key)
            },
            Self::Multi(vs) => {
                let mut collections:Vec<String> = Vec::new();
                if let Ok(match_re) = Regex::new(key){
                    let _ = vs.iter().map(|v|{
                        for _ in match_re.captures_iter(v){
                            if !collections.contains(&v){
                                collections.push(v.to_owned());
                                break
                            }
                        }
                    }).collect::<Vec<_>>();
                }
                Self::Multi(collections)
            }
        }

    } 

    pub fn to_re(&self, key:&str) -> Self{
        match self{
            Self::Single(v) => {
                let mut collections:Vec<String> = Vec::new();
                if let Ok(match_re) = Regex::new(key){
                    for cap in match_re.captures_iter(v){
                        let vv = cap[0].to_string();
                        if !collections.contains(&vv){
                            collections.push(vv)
                        }
                        
                    }
                }
                Self::Multi(collections)
            },
            Self::Multi(_) => {
                self.merge_with("\n").to_re(key)
            }
        }
        
    }
    pub fn to_split(&self, sep:&str) -> Self{
        match self{
            Self::Single(v) =>{
                Self::Multi(v.split(sep).filter_map(|x| if x.trim().len() >0 {Some(x.to_string())}else{None}).collect())
            },
            Self::Multi(_) => self.merge_with("\n").to_split(sep)
        }
    }

    pub fn to_css(&self, cssselect:&str) -> Self{
        match self{
            Self::Single(v)=>{
                let mut collections:Vec<String> = Vec::new();
                let doc = Document::from(v.as_str());
                let mut cls_q = String::new();
                let mut id_q = String::new();
                let mut attr_q = String::new();
                let mut left_css = cssselect.to_string();
                let data_or_html = if cssselect.contains("|"){
                    left_css = left_css.replace("|","");
                    true
                }else{
                    false
                };
                if cssselect.contains("."){
                    if let Some(cls) = CLASSS_RE.captures_iter(&left_css).next(){
                        let c = cls[0].to_string();
                        cls_q = (&c[1..]).to_string();
                        left_css = CLASSS_RE.replace(&left_css, "").to_string();
                        // log::info!("class:{}",cls_q);
                    };
                }
                if cssselect.contains("#"){
                    if let Some(cls) = ID_RE.captures_iter(cssselect).next(){
                        let c = cls[0].to_string();
                        id_q = (&c[1..]).to_string();
                        left_css = ID_RE.replace(&left_css, "").to_string();
                        // log::info!("id:{}",id_q);
                    };
                }
                if cssselect.contains("[") &&  cssselect.contains("]"){
                    if let Some(atr) = ATTR_RE.captures_iter(cssselect).next(){
                        let c = atr[1].to_string();
                        attr_q = (&c[1..c.len()-1]).to_string();
                        left_css = ATTR_RE.replace(&left_css, "").to_string();
                        // log::info!("attr:{}",attr_q);
                    }
                }
                doc.find(Name(left_css.trim())).filter_map(|node|{
                    let tmp_node = if cls_q.len()> 0{
                        if let Some(s) = node.attr("class"){
                            if s == cls_q{
                                Some(node)
                            }else{
                                None
                            }
                        }else{
                            None
                        }
                    }else{
                        Some(node)
                    };
                    let tmp_node = if let Some(no) = tmp_node{
                        if id_q.len() > 0{
                            if let Some(i) = no.attr("id"){
                                if i == id_q{
                                    Some(node)
                                }else{
                                    None
                                }
                            }else{
                                None
                            }
                        }else{
                            Some(node)
                        }
                    }else{
                        None
                    };
                    if let Some(no) = tmp_node{
                        if attr_q.len() > 0{
                            if attr_q.contains("="){
                                let ks:Vec<&str> = attr_q.split("=").collect();
                                if let Some(v) = no.attr(ks[0].trim()){
                                    if v == ks[1].trim(){
                                        Some(no)
                                    }else{
                                        None
                                    }
                                }else{
                                    None
                                }
                            }else{
                                if let Some(_) = no.attr(&attr_q){
                                    Some(no)
                                }else{
                                    None
                                }
                            }
                        }else{
                            Some(no)
                        }
                    }else{
                        None
                    }
                }).for_each(|no|{
                    if data_or_html{
                        collections.push(no.html());
                    }else{
                        if attr_q.len() > 0 {
                            if let Some(v) = no.attr(attr_q.trim()){
                                if !collections.contains(&v.to_string()) {
                                    collections.push(v.to_string());
                                }
                            }
                        }else{
                            let vv = no.text().trim().to_string();
                            if !collections.contains(&vv) {
                                collections.push(vv);
                            }
                        }
                    }
                    
                });
                
                Self::Multi(collections)
            },
            Self::Multi(_) => self.merge_with("\n").to_css(cssselect)
        }
        
    }
    pub fn to_jq(&self, jq_query_str:&str) -> Self{
        match self{
            Self::Single(v) =>{
                let mut collections:Vec<String> = Vec::new();
                let json_v:serde_json::Value = match serde_json::from_str(v){
                    Ok(a) => a,
                    _ => serde_json::json!("")
                };
                for jq_query_key in jq_query_str.split("|"){
                    let mut json_v1 = json_v.clone();
                    for k in jq_query_key.split("."){
                        if let Some(v) = json_v1.get(&k){
                            json_v1 = v.clone();
                        }
                    }
                    if !collections.contains(&json_v1.to_string()){
                        collections.push(json_v1.to_string())
                    }
                }
                Self::Multi(collections)
            },
            Self::Multi(_) => self.merge_with("\n").to_jq(jq_query_str)
        }
    }

    pub fn to_string(&self) ->String{
        match self{
            Self::Single(ref f) => f.to_string(),
            Self::Multi(_) => self.merge_with("\n").to_string()
        }
    }
    pub fn to_vec(&self) -> Vec<String>{
        match self{
            Self::Single(_) => self.to_split("\n").to_vec(),
            Self::Multi(f) => f.clone()
        }
    }

    pub fn to_json(&self, sep:&str, eq:&str) -> Self{
        /*
        example:  some thing split by "，"  Hash by ":"
          ex:   " X=sss  f=xxxx"   can be json with  to_json(" ", "=") =>  {"X":"sss", "f": "xxxx"} 
        to_json(",", ":") 
        */
        match self{
            Self::Single(_) => self.to_split("\n").to_json(sep, eq),
            Self::Multi(f) => {
                let v: Vec<String> = f.iter().filter_map(|x| {
                    let mut h: HashMap<String, String> = HashMap::new();
                    if x.contains(sep) && x.contains(eq) {
                        let mut vss =String::new();
                        for v in x.split(sep){
                            if v.contains(eq){
                                vss.push_str(&v);
                                if vss.contains('"') && vss.to_string().chars().fold(0, |acc, ch|{ if ch == '"' {acc + 1}else{acc}})% 2 ==1{
                                    vss.push_str(sep);
                                }else{
                                    let one_pair = vss.replace("\"","");
                                    let mut ks = one_pair.split(eq);
                                    let k = ks.next().unwrap();
                                    let v:String = ks.collect::<Vec<&str>>().join(eq);
                                    h.insert(k.to_string(), v);
                                    vss = "".to_string();
                                }
                                
                            }
                        }
                        if h.len() == 0{
                            None
                        }else{
                            Some(serde_json::json!(h).to_string())
                        }
                    }else{
                        Some(serde_json::json!(x).to_string())
                    }
                } ).collect();
                Self::Multi(v)
            }
        }
    }
}

pub trait WraperParser {
    fn as_wraper(&self) -> Wraper;
    fn wraper_with(&self, wraper_str:&str) -> Wraper;
}

impl <'a>WraperParser for &'a str{
    fn as_wraper(&self) -> Wraper{
        if self.trim().starts_with("/") || self.trim().starts_with("~/"){
            if Path::new(self.trim()).exists(){
                if let Some(buf) = self.file_open_str(){
                    return Wraper::Single(buf);
                }
            }
        }
        Wraper::Single(self.to_string())
        
    }
    fn wraper_with(&self, wraper_str:&str) -> Wraper{
        //!
        //! wraper_str:   [main , "arg1 , same in arg1", arg2 ]
        //!     main : to_json, to_re, to_css, to_split

        let mut tmp_wraper = self.as_wraper();
        for block in wraper_str.split("|"){
            if let Some(single) = block.trim().from_64(){
                let default_arg1 = "\n";
                let default_arg2 = " ";
                let mut api_and_args = single.split_skip(",",'"');
                match  api_and_args.len() {
                    1 => {
                        api_and_args.push(default_arg1.to_string());
                        api_and_args.push(default_arg2.to_string());
                    },
                    2 => {
                        api_and_args.push(default_arg2.to_string());
                    },
                    _ => {}
                };
                log::info!("{} ({}, {})", &api_and_args[0].green(), &api_and_args[1].yellow(), &api_and_args[2].blue());
                match api_and_args[0].as_str(){
                    "to_json" => { tmp_wraper = tmp_wraper.to_json(&api_and_args[1],&api_and_args[2]);},
                    "to_split" => { tmp_wraper = tmp_wraper.to_split(&api_and_args[1]);},
                    "to_css" => {tmp_wraper = tmp_wraper.to_css(&api_and_args[1]);},
                    "to_re" => {tmp_wraper = tmp_wraper.to_re(&api_and_args[1]);},
                    "to_grep" => {tmp_wraper = tmp_wraper.to_grep(&api_and_args[1]);},
                    "to_replace" => {tmp_wraper = tmp_wraper.to_replace(&api_and_args[1], &api_and_args[2]);},
                    "to_append" => {tmp_wraper = tmp_wraper.to_append(&api_and_args[1]);},
                    "to_jq" => {tmp_wraper = tmp_wraper.to_jq(&api_and_args[1]);},
                    _ => {}
                }
            }else{
                log::error!("not valid args in wrapper: {}", block.red());
            }
        }
        tmp_wraper
    }
}
impl WraperParser for String {
    fn as_wraper(&self) -> Wraper{
        Wraper::Single(self.to_string())
    }
    fn wraper_with(&self, wraper_str:&str) -> Wraper{
        self.as_str().wraper_with(wraper_str)
    }
}

impl Display for Wraper {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self{
            Self::Single(ref v) => write!(f,"{}", v),
            Self::Multi(_) => self.merge_with("\n").fmt(f) 
        }
    }
}

